package com.soft.base.util;

import com.alibaba.fastjson.annotation.JSONField;
import com.google.common.base.Objects;
import com.xiaoleilu.hutool.util.CollectionUtil;
import com.xiaoleilu.hutool.util.StrUtil;
import lombok.Getter;
import lombok.Setter;

import java.util.*;

/**
 * class
 *
 * @author user
 * @date 2020/06/05
 */
@SuppressWarnings("ALL")
public class TreeUtil {

    @SuppressWarnings("AlibabaAvoidStartWithDollarAndUnderLineNaming")
    public static abstract class AbstractNode<T> {
        @Getter
        @Setter
        private Long id;
        @Getter
        @Setter
        private Long pid;
        @Getter
        @Setter
        private Integer sort;
        @Getter
        @Setter
        private String name;
        @Getter
        @Setter
        private Integer count;
        @Getter
        @Setter
        @JSONField(name = "children")
        private List<T> children;
    }

    public static abstract class AbstractNodeOption<T> {
        @Getter
        @Setter
        private Long id;
        @Getter
        @Setter
        private Long pid;
        @Getter
        @Setter
        private Integer sort;
        @Getter
        @Setter
        private Integer level;
        @Getter
        @Setter
        private String title;
        @Getter
        @Setter
        private Integer count;
    }

    public static <T extends AbstractNode, TO extends AbstractNodeOption> List<TO> option(List<T> tree, Class<TO> cls) {
        List<TO> result = new ArrayList<>();
        optionProcess(result, tree, cls, 0);
        return result;
    }

    public static <T extends AbstractNode, TO extends AbstractNodeOption> List<TO> optionIndent(List<T> tree, Class<TO> cls) {
        List<TO> result = new ArrayList<>();
        optionProcess(result, tree, cls, 0);
        for (TO to : result) {
            to.setTitle(StrUtil.repeat("|---", to.getLevel()) + to.getTitle());
        }
        return result;
    }

    public static <T extends AbstractNode> List<Long> allChildIds(List<T> nodes, Long id) {
        ArrayList<Long> ids = new ArrayList<>();
        allChildIdsProcess(ids, nodes, id);
        return ids;
    }

    private static <T extends AbstractNode> void allChildIdsProcess(List<Long> ids, List<T> nodes, Long id) {
        for (T node : nodes) {
            if (Objects.equal(node.getPid(), id)) {
                ids.add(node.getId());
                allChildIdsProcess(ids, nodes, node.getId());
            }
        }
    }

    private static <T extends AbstractNode, TO extends AbstractNodeOption> void optionProcess(List<TO> result, List<T> tree, Class<TO> cls, Integer level) {
        for (T t : tree) {
            try {
                TO to = cls.newInstance();
                to.setId(t.getId());
                to.setPid(t.getPid());
                to.setSort(t.getSort());
                to.setLevel(level);
                to.setTitle(t.getName());
                result.add(to);
            } catch (Exception e) {
            }
            if (CollectionUtil.isNotEmpty(t.getChildren())) {
                optionProcess(result, t.getChildren(), cls, level + 1);
            }
        }
    }

    public static <T extends AbstractNodeOption> void processCount(List<T> list, Map<Long, Integer> countMap) {
        for (T t : list) {
            t.setCount(0);
        }
        Map<Long, List<T>> map = listChain(list);
        for (Map.Entry<Long, Integer> entry : countMap.entrySet()) {
            List<T> v = map.get(entry.getKey());
            if (null == v) {
                continue;
            }
            v.forEach(o -> o.setCount(entry.getValue() + o.getCount()));
        }
    }

    public static <T extends AbstractNode> void processTreeCount(List<T> nodes, Map<Long, Integer> countMap) {
        Map<Long, List<T>> map = listTreeChain(nodes);
        for (Map.Entry<Long, Integer> entry : countMap.entrySet()) {
            List<T> v = map.get(entry.getKey());
            if (null == v) {
                continue;
            }
            v.forEach(o -> {
                if (null == o.getCount()) {
                    o.setCount(0);
                }
                o.setCount(entry.getValue() + o.getCount());
            });
        }
    }

    public static <T extends AbstractNodeOption> Map<Long, List<T>> listChain(List<T> list) {
        HashMap<Long, List<T>> map = new HashMap<>(list.size());
        for (T t : list) {
            List<T> chain = new ArrayList<>();
            chain.add(t);
            Long pid = t.getPid();
            for (int i = 0; i < Integer.MAX_VALUE && pid > 0; i++) {
                for (T t1 : list) {
                    if (t1.getId().equals(pid)) {
                        chain.add(t1);
                        pid = t1.getPid();
                        break;
                    }
                }
            }
            Collections.reverse(chain);
            map.put(t.getId(), chain);
        }
        return map;
    }

    public static <T extends AbstractNode> Map<Long, List<T>> listTreeChain(List<T> nodes) {
        Map<Long, List<T>> map = new HashMap<>(nodes.size());
        Map<Long, T> nodeMap = new HashMap<>(nodes.size());
        for (T t : nodes) {
            listTreeChain(t, nodeMap, map);
        }
        return map;
    }

    private static <T extends AbstractNode> void listTreeChain(T node, Map<Long, T> nodeMap, Map<Long, List<T>> map) {
        nodeMap.put(node.getId(), node);
        List<T> chain = map.computeIfAbsent(node.getId(), k -> new ArrayList<>());
        chain.add(node);
        Long pid = node.getPid();
        for (int i = 0; i < Integer.MAX_VALUE && pid > 0; i++) {
            if (!nodeMap.containsKey(pid)) {
                break;
            }
            chain.add(nodeMap.get(pid));
            pid = nodeMap.get(pid).getPid();
        }
        Collections.reverse(chain);
        if (CollectionUtil.isNotEmpty(node.getChildren())) {
            for (Object o : node.getChildren()) {
                listTreeChain((T) o, nodeMap, map);
            }
        }
    }

    public static <T extends AbstractNode> List<T> merge(List<T> list) {
        CollectionUtil.sort(list, (o1, o2) -> {
            if (Objects.equal(o1.getSort(), o2.getSort())) {
                return 0;
            }
            if (o1.getSort() == null) {
                if (o2.getSort() == null) {
                    return 0;
                } else {
                    return 1;
                }
            } else {
                if (o2.getSort() == null) {
                    return -1;
                } else {
                    return o1.getSort() > o2.getSort() ? 1 : -1;
                }
            }
        });
        Map<Long, T> map = new LinkedHashMap<>();
        for (T t : list) {
            if (t.getChildren() == null) {
                t.setChildren(new ArrayList<T>());
            }
            map.put(t.getId(), t);
        }
        List<T> result = new ArrayList<>();
        for (Map.Entry<Long, T> entry : map.entrySet()) {
            final T v = entry.getValue();
            if (map.containsKey(v.getPid())) {
                map.get(v.getPid()).getChildren().add(v);
            } else {
                result.add(v);
            }
        }
        return result;
    }

    public static <T extends AbstractNode> boolean deletable(List<T> list, Long id) {
        for (T t : list) {
            if (Objects.equal(t.getPid(), id)) {
                return false;
            }
        }
        return true;
    }

}
